from fill_in_error import __, assert_equal

# list is a container to gather some items together

class AboutList:
    def create_an_empty_list():
        new_list = []
        assert_equal(new_list, __)

    def or_this_way():
        new_list = list()
        assert_equal(new_list, __)

    def create_a_list_with_some_value():
        # normal form
        new_list = [0, 1, 2]
        assert_equal(new_list, __)

        # some magic
        new_list = [i for i in range(0, 3)] # range(x, y) means [x, y)
        assert_equal(new_list, __)

        # want same number?
        new_list = [0 for i in range(3)] # range(x) means [0, x)
        assert_equal(new_list, __)

    def len_of_a_list():
        # len means how many element are in the list
        
        new_list = []
        assert_equal(len(new_list), __)
        new_list = [1, 2, 3]
        assert_equal(len(new_list), __)

    def access_a_list():
        new_list = [1, 2, 3]

        # access the first element
        assert_equal(new_list[0], __)
        # access the second
        assert_equal(new_list[1], __)
        # the last
        assert_equal(new_list[-1], __)
        # the second-to-last
        assert_equal(new_list[-2], __)

        # access a slice of list [x:y] means [x, y)
        assert_equal(new_list[0:2], __)
        # [:y] means [0, y)
        assert_equal(new_list[:2], __)
        # [x:] means from xth to the last(inclusive!)
        assert_equal(new_list[1:], __)
        # [x:-1] means from xth to the last(exclusive)
        assert_equal(new_list[1:-1], __)
        # [:] means all
        assert_equal(new_list[:], __)
        # [x:x] gets an empty list
        assert_equal(new_list[1:1], __)
    
    def append_on_a_list():
        new_list = []
        assert_equal(new_list, __)
        
        # append means put a element to the end of the list
        new_list.append(1)
        assert_equal(new_list, __)
        new_list.append(2)
        assert_equal(new_list, __)

        # you can even append different type of element!
        new_list.append("numbers and string are all objects")
        assert_equal(new_list, __)
    
    def pop_from_a_list():
        # pop means remove a element and return it
        
        new_list = [1, 2, 3]
        assert_equal(new_list, __)
        
        # pop without index remove the last element
        assert_equal(new_list.pop(), __)
        assert_equal(new_list, __)

        # pop with index remove the index th element
        assert_equal(new_list.pop(0), __)
        assert_equal(new_list, __)
        
    def insert_into_a_list():
        # insert means a element before index
        
        new_list = ['a', 'b', 'c']
        assert_equal(new_list, __)

        new_list.insert(0, 'before a')
        assert_equal(new_list, __)

        # this is the same as new_list.append('after c')
        new_list.insert(len(new_list), 'after c')
        assert_equal(new_list, __)

        # THINK ABOUT IT:
        # insert(len(new_list)) and append
        # which one is better? why?

    def reverse_a_list():
        new_list = [1, 2, 3]
        
        # new_list.reverse() modifies the list itself
        new_list.reverse()
        assert_equal(new_list, __)
        # however, reversed(new_list) returns a iterator
        # and does not change the oringinal list
        # if you want to know more about iterator,
        # check about_iterator.py
        another_list = list(reversed(new_list))
        assert_equal(new_list, __)
        assert_equal(another_list, __)

    def sort_on_a_list():
        new_list = [1, 3, 2]
        assert_equal(new_list, __)
        
        # new_list.sort() modifies itselt and returns None
        new_list.sort()
        assert_equal(new_list, __)

        # reverse=True means sort from higher to lower
        new_list = [1, 3, 2]
        assert_equal(new_list, __)
        new_list.sort(reverse=True)
        assert_equal(new_list, __)

        # sorted(new_list) returns a new sorted list
        # and does not change the original list
        new_list = [1, 3, 2]
        assert_equal(new_list, __)
        another_list = sorted(new_list)
        assert_equal(new_list, __)
        assert_equal(another_list, __)

        # you can also use reversed flag on sorted()
        new_list = [1, 3, 2]
        another_list = sorted(new_list, reverse=True)
        assert_equal(another_list, __)

        # strings can be sorted too!
        new_list = ['abc', 'aba', 'aab']
        new_list.sort()
        assert_equal(new_list, __)

        # advanced warning! it is ok if you can't understand it now

        # you can use sort more flexibly with key
        new_list = ['abc', 'aba', 'aab']
        new_list.sort(key=lambda s: s[:2])
        assert_equal(new_list, __)
        
    def add_two_list():
        # use extend to append a list of element at once
        new_list = [1, 2, 3]
        assert_equal(new_list, __)
        new_list.extend([4, 5, 6])
        assert_equal(new_list, __)

        # use + to get a new larger list
        one_list = [1, 2, 3]
        another_list = [4, 5, 6]
        larger_list = one_list + another_list
        assert_equal(larger_list, __)

        # so += has the same effect as new_list.extend!
        new_list = [1, 2, 3]
        new_list += [4, 5, 6]
        assert_equal(new_list, __)

        # some magic
        # remember we can use [0 for i in range(n)] 
        # to get a [0...0] list?
        # here is another way
        new_list = [0] + [0] + [0]
        assert_equal(new_list, __)
        # emm, we can get better, using multiply *!
        new_list = [0] * 3
        assert_equal(new_list, __)
    
    def count_times_of_a_element():
        new_list = [1, 2, 2]
        
        assert_equal(new_list.count(1), __)
        assert_equal(new_list.count(2), __)
        assert_equal(new_list.count(0), __)

    def clear_a_list():
        new_list = [1, 2, 3]
        
        new_list.clear()
        assert_equal(new_list, __)
    
    def search_an_element_in_the_list():
        new_list = [1, 2, 3, 1, 1]
        assert_equal(new_list.index(1), __)

        # you can specify the start index and end index
        assert_equal(new_list.index(1, 1), __)
        assert_equal(new_list.index(1, 1, 4), __)

        # warning! A ValueError will return if no match element!
        # new_list.index(5) # uncomment this line to see what happens

        
    def copy_a_list():
        # copy means return a new same list
        
        new_list = [1, 2]
        another_list = new_list
        another_list.append(3)
        assert_equal(another_list, __)
        assert_equal(new_list, __)

        # here copy comes
        new_list = [1, 2]
        another_list = new_list.copy()
        another_list.append(3)
        assert_equal(another_list, __)
        assert_equal(new_list, __)